﻿using System.Net;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
//using OPCUaClient;
//using OPCUaClient.Objects;

namespace OPCTest_1022
{
    internal class Program
    {
        static string TestOPCUrl = "opc.tcp://127.0.0.1:49320";
        static string PLCOPCUrl = "opc.tcp://192.168.1.180:4840";
        #region 基于OPCUAClient 
        ////标准OPCUA测试
        //static async Task Main1(string[] args)
        //{
        //    //    UaClient client = new UaClient("OPCT_1022", "opc.tcp://127.0.0.1:49320", true, true,"AAA","88888888888888");
        //    //kepserver测试
        //    UaClient client = new UaClient("OPCT_1022", "opc.tcp://127.0.0.1:49320", false, true);
        //    //西门子1500plc测试，好像处理不了
        //    // UaClient client = new UaClient("OPCT_1022", "opc.tcp://192.168.8.10:4840", false, true);
        //    await client.ConnectAsync();
        //    //这种样式，并非ns=2...这种
        //    //var res = client.Read("通道 1.设备 1.K2"); 
        //    List<Device>? des = await client.DevicesAsync();
        //    des.ForEach(d =>
        //    {
        //        Console.WriteLine("设备名称：" + d.Name);
        //        d.Groups.ForEach(z =>
        //        {
        //            Console.WriteLine("\t组名称：" + z.Name + ":");
        //            z.Tags.ForEach(x =>
        //            {
        //                Console.WriteLine("\t\t" + x.Name);
        //            });
        //            ScanAll(z);
        //        });

        //    });
        //    Console.WriteLine("end------");
        //    Console.Read();
        //}
        //static void ScanAll(Group g)
        //{
        //    g.Groups.ForEach((z) =>
        //    {
        //        Console.WriteLine($"\t\t{z.Name}");
        //        z.Tags.ForEach(x =>
        //        {
        //            Console.WriteLine("\t\t\t" + x.Name);
        //        });
        //        ScanAll(z);
        //    });
        //}
        #endregion
        #region upcua .netfoundation官方


        //OPCUA测试，此处启动入口
        static async Task Main(string[] args)
        {
            var session = await 匿名方式访问();
            // var session = await 基于账号密码登录方式();

            // var session = await 认证登录();

            // await 浏览服务器节点(session);

            //  await 数据读取示例(session);

            // await 数据写入示例(session);

            await 订阅模式(session);

            Console.WriteLine("end...");
            Console.Read();
        }
        static async Task<Session> 匿名方式访问()
        {
            //配置对象
            ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
            //匿名方式下直接实例化即可
            applicationConfiguration.ClientConfiguration = new ClientConfiguration();
            //配置地址相关信息，并非直接new ，而是通过CoreClientUtils,（ new EndpointDescription()好像不行？会报错）

            var endpointdec = CoreClientUtils.SelectEndpoint(PLCOPCUrl, false);
            //会报错
            //var endpointdec = new EndpointDescription("opc.tcp://127.0.0.1:49320");

            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointdec);

            //创建访问对象
            Session session = await Session.Create(applicationConfiguration,
                endpoint, false, "AAA", 5000, new UserIdentity(), new string[] { });
            return session;
        }
        static async Task<Session> 基于账号密码登录方式()
        {
            // 1 
            ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
            applicationConfiguration.ClientConfiguration = new ClientConfiguration();
            // 1.1
            CertificateValidator validator = new CertificateValidator();
            //自动允许不受信任的证书，此处和下方验证回调达成效果不一致(尚不清楚，目前该项配置无效)，即忽略(允许)不受信任的证书
            validator.AutoAcceptUntrustedCertificates = true;
            //验证回调，登录过程中的错误处理
            validator.CertificateValidation += (x, y) =>
            {
                //也可以在此配置是否允许相关错误,比如此处不信任的证书放行
                if (y.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
                {   //认为是正常错误
                    y.Accept = true;
                }
            };

            SecurityConfiguration securityConfiguration = new SecurityConfiguration();
            //拒绝SH1签名的证书 == false
            securityConfiguration.RejectSHA1SignedCertificates = false;

            await validator.Update(securityConfiguration);

            applicationConfiguration.CertificateValidator = validator;

            // 2
            EndpointDescription description = CoreClientUtils.SelectEndpoint(TestOPCUrl, false);

            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, description);

            Session session = await Session.Create(
                applicationConfiguration,
                endpoint,
                true,
                "AAA",
                5000,
                new UserIdentity("AAA",
                "88888888888888"),
                new string[] { });
            return session;
        }
        static async Task<Session> 认证登录()
        {
            // 1 
            ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
            applicationConfiguration.ClientConfiguration = new ClientConfiguration();
            // 1.1
            CertificateValidator validator = new CertificateValidator();
            //验证回调，登录过程中的错误处理
            validator.CertificateValidation += (x, y) =>
            {
                //也可以在此配置是否允许相关错误,比如此处不信任的证书放行
                if (y.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
                {   //认为是正常错误
                    y.Accept = true;
                }
            };

            applicationConfiguration.CertificateValidator = validator;

            #region 认证连接配置
            //表示客户端名称
            string appname = "AAAClient";
            applicationConfiguration.ApplicationName = appname;
            //认证uri，格式建议如此做
            applicationConfiguration.ApplicationUri = $"urn:{Dns.GetHostName()}:{appname}";

            applicationConfiguration.SecurityConfiguration = new SecurityConfiguration()
            {

                //必须设置 
                RejectSHA1SignedCertificates = false,
                //必须设置
                MinimumCertificateKeySize = 1024,
                ApplicationCertificate = new CertificateIdentifier()
                {
                    //证书类型
                    StoreType = CertificateStoreType.X509Store,
                    //指服务器地址，填此即可
                    StorePath = $"CurrentUser\\{appname}",
                    //建议和appname保持一致
                    SubjectName = appname
                }
            };

            //客户端配置信息验证
            await applicationConfiguration.Validate(ApplicationType.Client);

            ApplicationInstance applicationInstance = new ApplicationInstance();
            applicationInstance.ApplicationConfiguration = applicationConfiguration;
            //生成证书并提交服务器认证
            await applicationInstance.CheckApplicationInstanceCertificate(false, 1024);
            #endregion


            // 2
            EndpointDescription description = CoreClientUtils.SelectEndpoint(TestOPCUrl, false);

            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, description);
            //当证书提交到服务器，但是服务器没信任也会出错
            Session session = await Session.Create(
                applicationConfiguration,
                endpoint,
                true,
                "AAA",
                5000,
                new UserIdentity(),
                new string[] { });
            return session;
        }
        static async Task 浏览服务器节点(Session session)
        {
            Browser browser = new Browser(session);
            //浏览节点，若是要浏览所有节点，需要进行递归
            var data = browser.Browse("ns=2;s=通道 1");
            foreach (var item in data)
            {

                //手动浏览
                // Console.WriteLine(item.BrowseName);
                Console.WriteLine(item.NodeId.ToString());
                foreach (var item1 in browser.Browse(item.NodeId.ToString()))
                {
                    Console.WriteLine("\t" + item1.NodeId.ToString());
                    foreach (var item2 in browser.Browse(item1.NodeId.ToString()))
                    {
                        Console.WriteLine("\t\t" + item2.NodeId.ToString());
                    }
                }
            }
        }
        static async Task 数据读取示例(Session session)
        {
            RequestHeader requestHeader = new RequestHeader();
            ReadValueIdCollection vals = new ReadValueIdCollection();
            vals.Add(new ReadValueId()
            {
                //设置为读取值
                AttributeId = Attributes.Value,
                //此处NodeID可为字符串形式
                NodeId = "ns=2;s=通道 1.设备 1.K1"
            });
            vals.Add(new ReadValueId()
            {
                //设置为读取值
                AttributeId = Attributes.Value,
                //此处NodeID可为字符串形式
                NodeId = "ns=2;s=通道 1.设备 1.K4"
            });
            var response = await session.ReadAsync(
                   requestHeader,
                   0,
                   TimestampsToReturn.Both,
                    vals,
                    CancellationToken.None
                   );
            //诊断信息
            // response.DiagnosticInfos
            //获取到的结果
            foreach (var item in response.Results)
            {
                //当数据是一个集合，
                //读取方式一，直接判断类型是否继承于集合的基类Array
                //if (item.Value.GetType().BaseType == typeof(Array))
                //{
                //    foreach (var it1 in (Array)item.Value)
                //    {
                //        Console.WriteLine(it1);
                //    }
                //}
                //读取方式二，判断ValueRank是否为1，
                if (item.WrappedValue.TypeInfo.ValueRank == 1)
                {
                    foreach (var it2 in (Int16[])item.WrappedValue.Value)
                    {
                        Console.WriteLine(it2);
                    }
                }
                else
                {

                    Console.WriteLine(item.Value);
                }
            }




        }
        static async Task 数据写入示例(Session session)
        {
            RequestHeader requestHeader = new RequestHeader();
            WriteValueCollection writeValues = new WriteValueCollection();
            writeValues.Add(new WriteValue()
            {
                AttributeId = Attributes.Value,
                //普通OPCUA节点地址
                //NodeId = "ns=2;s=通道 1.设备 1.K1",
                //西门子1500PLC节点地址，经过测试，无问题
                NodeId = "ns=3;s=\"数据块_1\".\"1\"",


                //此处注意类型要一致，比如opc服务器中类型为word，则此处必须为ushort，即使short也不行
                Value = new DataValue() { Value = (float)666.666 }
            });
            var res = await session.WriteAsync(requestHeader, writeValues, CancellationToken.None);
            foreach (var item in res.Results)
            {
                Console.WriteLine(StatusCode.IsGood(item));
            }
        }
        static async Task 订阅模式(Session session)
        {
            ///声明对象，参数如此即可
            var subs = new Subscription(session.DefaultSubscription);
            MonitoredItem item = new MonitoredItem()
            {
                //普通节点
                StartNodeId = "ns=2;s=通道 1.设备 1.K1",
                //西门子plc的OPCUA服务器节点地址
                //  StartNodeId = "ns=3;s=\"数据块_1\".\"1\"",
                //西门子plc新增的服务器接口，该接口添加的一个1hz的时钟存储器
                //  StartNodeId = "ns=4;i=2",
                AttributeId = Attributes.Value,
            };
            //当监视的节点发生变化时回调
            item.Notification += (x, y) =>
            {
                Console.WriteLine($"{x.StartNodeId}节点数据发生变化：" + ((MonitoredItemNotification)y.NotificationValue).Value.Value);
            };
            subs.AddItem(item);
            //添加进去
            session.AddSubscription(subs);
            //在服务器上创建并监视所有添加的项目
            await subs.CreateAsync();

        }
        #endregion


        #region 基于opcuahelper  此库运行异常
        static void Main3(string[] args)
        {
            OpcUaHelper.OpcUaClient client = new OpcUaHelper.OpcUaClient();
            //此处可以配置多种认证方式，比如匿名？，账号密码？证书等？
            client.UserIdentity = new UserIdentity("ACT", "88888888888888");
            client.ConnectServer(TestOPCUrl).GetAwaiter().GetResult();
            var rd1 = client.ReadNodeAsync<ushort>("ns=2;s=通道 1.设备 1.K1").GetAwaiter().GetResult();
            Console.WriteLine(rd1);




            Console.WriteLine("end...");
            Console.ReadLine();
        }
        #endregion

    }
}